﻿using System;
using System.Collections.Generic;
using System.Text;
using GeneticAlgorithms.Alphabets;
using GeneticAlgorithms.Tests.Utils;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace GeneticAlgorithms.Tests.Alphabets
{
    public class AlphabetTestHelper<TAlphabet, TGene> : GeneticAlgorithms.Tests.Alphabets.IAlphabetTestHelper where TAlphabet : IAlphabet<TGene>
    {
        public AlphabetTestHelper(Action<TGene, int> geneChecker)
            : this(geneChecker, DefaultCreateAlphabet)
        {
        }

        public AlphabetTestHelper(Action<TGene, int> geneChecker, Func<TAlphabet> createAlphabet)
            : this(geneChecker, createAlphabet, new HashSet<TGene>(), new HashSet<TGene>(), false)
        {
        }

        public AlphabetTestHelper(
            Action<TGene, int> geneChecker, 
            Func<TAlphabet> createAlphabet,
            HashSet<TGene> includedValues,
            HashSet<TGene> excludedValues,
            bool enforceExistance)
        {
            this.createAlphabet = createAlphabet;
            this.geneChecker = geneChecker;
            this.includedValues = includedValues;
            this.excludedValues = excludedValues;
            this.enforceExistance = enforceExistance;
        }

        bool enforceExistance;
        private static TAlphabet DefaultCreateAlphabet()
        {
            return Activator.CreateInstance<TAlphabet>();
        }

        Func<TAlphabet> createAlphabet;
        Action<TGene, int> geneChecker;
        HashSet<TGene> includedValues;
        HashSet<TGene> excludedValues;

        public virtual void DoConvertFromIntTest()
        {
            TAlphabet target = createAlphabet();
            int step = (int)Math.Max(1, target.Range / 10000);
            int limit = target.Range - step;
            for (int i = 0; i <= limit; i += step)
            {
                TGene result = target.ConvertFromInt(i);
                bool deleted = includedValues.Remove(result);
                if (enforceExistance)
                {
                    Assert.IsTrue(deleted, "Element " + result.ToString() + " should not be among the results or appears in duplicate.");
                }
                Assert.IsFalse(excludedValues.Contains(result));
                geneChecker(result, i);
            }

            Assert.AreEqual(0, includedValues.Count, 
                "There were values that were expected to be returned but weren't. Extract:"
                + GetExtract(includedValues));
        }

        private string GetExtract(HashSet<TGene> includedValues)
        {
            StringBuilder sb = new StringBuilder();

            foreach (TGene gene in includedValues)
            {
                if (sb.Length > 100)
                {
                    break;
                }

                sb.Append(gene);
            }

            return sb.ToString();
        }

        public virtual void DoFailedConvertTest()
        {
            TAlphabet target = createAlphabet();
            if (target.Range < Int32.MaxValue)
            {
                ExtraAssert.MethodThrowsArgumentOutOfRangeException(
                    delegate { target.ConvertFromInt(target.Range); },
                    "value");

                ExtraAssert.MethodThrowsArgumentOutOfRangeException(
                    delegate { target.ConvertFromInt(Int32.MaxValue); },
                    "value");
            }
        }
    }
}
